package com.ipan.kits.io;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 视频处理工具
 * 
 * 使用ffmepg工具进行视频处理，需要先安装ffmepg；
 * 
 * 下载地址：http://ffmpeg.org/
 * 
 * @author iPan
 * @version 2018-07-13
 */
public class VideoUtil {
	private static final Pattern EXEC_CODECS_SUCC_PATTERN = Pattern.compile("^.*Qavg: \\d+.*$", Pattern.MULTILINE | Pattern.DOTALL);
	private Logger log = LoggerFactory.getLogger(this.getClass());
	private static final String CMD_ENCODE = "GBK";
	private static final String EOL = System.getProperty("line.separator");
	private String ffmpegPath = null; 
	
	private VideoUtil(String ffmpegPath) {
		this.ffmpegPath = ffmpegPath;
	}
	
	public static VideoUtil getInstance(String path) {
		return new VideoUtil(path);
	}
	
	/**
	 * 获取分辨率
	 * width: 宽度
	 * height：高度
	 * rotate：旋转角度
	 * 注意：使用ffmpeg获得的视频文件信息，width一定是大于height；
	 */
	// MP4 Info: Stream #0:0(eng): Video: h264 (Baseline) (avc1 / 0x31637661), yuv420p, 3840x2160, 8316 kb/s, SAR 1:1 DAR 16:9, 30.06 fps, 30 tbr, 90k tbn, 180k tbc (default)
    // MOV Info: Stream #0:0(eng): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 720x1280 [SAR 1:1 DAR 9:16], 1514 kb/s, 29.83 fps, 29.83 tbr, 11456 tbn, 59.67 tbc (default)
	public static Map<String, Integer> parseDpi(String result) {
		if (result == null || result.length() < 1) {
			return null;
		}
		Map<String, Integer> map = new HashMap<String, Integer>();
		String[] arr = result.split(EOL);
		for (String line : arr) {
			if (line.contains("Video:")) {
				String[] tmpArr = line.replaceAll("\\((.*?)\\)", "").split(",");
				String tmpwh = tmpArr[2].trim();
				int spIndex = tmpwh.indexOf(' ');
				if (spIndex >= 0) {
					tmpwh = tmpwh.substring(0, spIndex);
				}
				String[] whArr = tmpwh.split("x");
				map.put("width", Integer.valueOf(whArr[0]));
				map.put("height", Integer.valueOf(whArr[1]));
			} else if (line.contains("rotate")) {
				String rotate = line.split(":")[1].trim();
				map.put("rotate", Integer.valueOf(rotate));
				if (map.size() >= 3) { // rotate出现在Video下面
					break;
				}
			}
		}
		return map.size() > 0 ? map : null;
	}
	
	public Map<String, Integer> getFileDpi(String filePath) {
		Map<String, Integer> map = null;
		List<String> cmds = new ArrayList<String>();
		cmds.add(ffmpegPath);
		cmds.add("-i");
		cmds.add(filePath);
		try {
			String result = exec(cmds, true);//System.out.println(result);
			map = parseDpi(result);
		} catch (Exception e) {
			log.error("获取分辨率", e);
		}
		return map;
	}

	/**
	 * 视频转码
	 * 
	 * 视频尺寸压缩有两种方式，每种压缩处理后都会丢失Rotation旋转参数：
	 * ffmpeg -i srcFile -s 1280*720 -y descFile
	 * 使用固定分辨率的方式压缩，转换后一律使用长度>高度（横屏）的方式；缺点是不能自动按比例压缩；
	 * 
	 * 横拍：ffmpeg -i srcFile -vf scale=1280:-1 -y descFile
	 * 竖拍：ffmpeg -i srcFile -vf scale=720:-1 -y descFile
	 * 这种方式可以自动识别长宽比例，但是这种方式需要判断原文件是横拍、竖拍，这样转换后可以支持长度<高度（竖屏）；
	 * 
	 * 在笔记本上测试比较耗时，测试20M、10秒的视频.mp4格式，需要50秒左右时间转为.mov格式（1M不到）；可以在后台开启线程来处理；
	 * 在服务区上测试，只需要几秒钟即可完成；
	 * 默认处理是等比例压缩为1280*720尺寸；
	 * 
	 * @param srcFile 用于指定要转换格式的文件,要截图的视频源文件
	 * @param descFile 格式转换后的的文件保存路径
	 * @return true 调用成功 false 调用失败
	 */
	public boolean executeCodecs(String srcFile, String descFile) {
		Map<String, Integer> dpiMap = getFileDpi(srcFile);
		if(dpiMap == null) {
			throw new RuntimeException("获取文件分辨率失败！(srcFile=" + srcFile + ", descFile=" + descFile);
		}
		Integer width = dpiMap.get("width");
//		Integer height = dpiMap.get("height");
		Integer rotate = dpiMap.get("rotate");
		
		// 创建一个List集合来保存转换视频文件为flv格式的命令
		List<String> convert = new ArrayList<String>();
		convert.add(ffmpegPath);
		convert.add("-i"); // 添加参数＂-i＂，该参数指定要转换的文件
		convert.add(srcFile);
//		convert.add("-qscale"); // 指定转换的质量
//		convert.add("6");
//		convert.add("-ab"); // 设置音频码率
//		convert.add("64");
//		convert.add("-ac"); // 设置声道数
//		convert.add("2");
//		convert.add("-ar"); // 设置声音的采样频率
//		convert.add("22050");
//		convert.add("-r"); // 设置帧频
//		convert.add("24");
		if (width > 1280) { // 分辨率超出1280才需要压缩
			convert.add("-vf");
			if (rotate != null) { // 旋转，说明是竖拍的；
				convert.add("scale=720:-1");
			} else { // 横拍的
				convert.add("scale=1280:-1");
			}
		}
		convert.add("-y"); // 添加参数＂-y＂，该参数指定将覆盖已存在的文件
		convert.add(descFile);

		boolean ret = true;
		try {
			String result = exec(convert, true);
			log.debug("//-- 视频转码结果 --//");
			log.debug(result);
//			log.info(result); //TODO: debug
			int lastLineIndex = result.substring(0, result.length() - 1).lastIndexOf(EOL);
			if (lastLineIndex > 0) {
				String lastLine = result.substring(lastLineIndex, result.length() - 1);
				ret = EXEC_CODECS_SUCC_PATTERN.matcher(lastLine).matches(); // 转换成功，最后一行是：[aac @ 00000200eea62440] Qavg: 499.979
			}
		} catch (Exception e) {
			ret = false;
			log.error("视频转码失败", e);
		}
		return ret;
	}

	/**
	 * 获取视频总时间
	 *
	 * @param videoPath 视频路径
	 * @return 时长几秒，出错是0；
	 */
	public int getVideoTime(String videoPath) {
		List<String> commands = new java.util.ArrayList<String>();
		commands.add(ffmpegPath);
		commands.add("-i");
		commands.add(videoPath);

		String result = exec(commands, true);
		// 从视频信息中解析时长
		String regexDuration = "Duration: (.*?), start: (.*?), bitrate: (\\d*) kb\\/s";
		Pattern pattern = Pattern.compile(regexDuration);
		Matcher m = pattern.matcher(result);
		if (m.find()) {
			int time = getTimelen(m.group(1));
			// 开始时间："+m.group(2)+",比特率："+m.group(3)+"kb/s");
			return time;
		}
		return 0;
	}

	// 格式:"00:00:10.68" 转化为秒数
	public static int getTimelen(String timelen) {
		int min = 0;
		String strs[] = timelen.split(":");
		if (strs[0].compareTo("0") > 0) {
			min += Integer.valueOf(strs[0]) * 60 * 60;// 秒
		}
		if (strs[1].compareTo("0") > 0) {
			min += Integer.valueOf(strs[1]) * 60;
		}
		if (strs[2].compareTo("0") > 0) {
			min += Math.round(Float.valueOf(strs[2]));
		}
		return min;
	}

	/**
     * 视频截图
     * 
     * 可以根据宽度、高度按照等比例缩放；
     * 建议：截图默认就截取第一张，不要自己去指定时间，指定时间截图可能会比较模糊；
     * 
     * @param srcFile    	要截图的视频源文件
     * @param descFile		添加截取的图片的保存路径
     * @param width			截图的宽
     * @param height		截图的高
     * @param time			第几秒开始截图（默认是1）
     * @return true 		调用成功，false 调用失败；
     */
    public boolean screenImage(String srcFile, String descFile, Integer width, Integer height, Integer time) {
        List<String> cmd = new ArrayList<String>();
        cmd.add(ffmpegPath);
        cmd.add("-i");
        cmd.add(srcFile); // 要截图的视频源文件
        cmd.add("-y");
        if (time != null && time > 0) { // 建议不要指定！
        	cmd.add("-ss"); // 添加参数＂-ss＂，该参数指定截取的起始时间
        	cmd.add(time.toString()); // 添加起始时间为第1秒
        }
//        cmd.add("-t"); // 添加参数＂-t＂，该参数指定持续时间
//        cmd.add("0.001"); // 添加持续时间为1毫秒
        cmd.add("-f");
        cmd.add("image2"); // cmd.add("mjpeg");
        if ((width != null && width > 0) && (height != null && height > 0)) {
        	cmd.add("-s"); // 添加参数＂-s＂，该参数指定截取的图片大小
            cmd.add(width + "*" + height); // 添加截取的图片大小为350*240
        } else if ((width != null && width > 0) && (height == null || height < 1)) {
        	cmd.add("-vf"); // 使用滤镜
            cmd.add("scale=" + width + ":-1");
        } else if ((width == null || width < 1) && (height != null && height > 0)) {
        	cmd.add("-vf"); // 使用滤镜
            cmd.add("scale=-1:" + height);
        }
        cmd.add(descFile); // 添加截取的图片的保存路径
 
        try {
        	String result = exec(cmd, true);
			log.debug("//-- 视频截图结果 --//");
			log.debug(result);
        } catch (Exception e) {
            log.error("视频截图失败", e);
        	return false;
        }
        return true;
    }
    
    public boolean screenImage(String srcFile, String descFile) {
    	return screenImage(srcFile, descFile, null, null, null);
    }
    
    public boolean screenImage(String srcFile, String descFile, Integer length, Integer time) {
    	Map<String, Integer> dpiMap = getFileDpi(srcFile);
		if(dpiMap == null) {
			throw new RuntimeException("获取文件分辨率失败！(srcFile=" + srcFile + ", descFile=" + descFile);
		}
		Integer videoWidth = dpiMap.get("width");
		Integer videoHeight = dpiMap.get("height");
		Integer videoRotate = dpiMap.get("rotate");
		List<String> cmd = new ArrayList<String>();
        cmd.add(ffmpegPath);
        cmd.add("-i");
        cmd.add(srcFile); // 要截图的视频源文件
        cmd.add("-y");
        if (time != null && time > 0) { // 建议不要指定！
        	cmd.add("-ss"); // 添加参数＂-ss＂，该参数指定截取的起始时间
        	cmd.add(time.toString()); // 添加起始时间为第1秒
        }
        cmd.add("-f");
        cmd.add("image2"); // cmd.add("mjpeg");
        if (length != null && length > 0) {
        	if (videoRotate != null || (videoRotate == null && videoWidth<videoHeight)) { // 竖拍
        		cmd.add("-vf"); // 使用滤镜
                cmd.add("scale=-1:" + length);
        	} else { // 横拍
        		cmd.add("-vf"); // 使用滤镜
                cmd.add("scale=" + length + ":-1");
        	}
        }
        cmd.add(descFile); // 添加截取的图片的保存路径
 
        try {
        	String result = exec(cmd, true);
			log.debug("//-- 视频截图结果 --//");
			log.debug(result);
        } catch (Exception e) {
            log.error("视频截图失败", e);
        	return false;
        }
        return true;
    }
    
    private static String exec(List<String> command, boolean syc) {
		StringBuilder buf = new StringBuilder();
		BufferedReader in = null;
		Process p = null;
		try {
			p = new ProcessBuilder(command).redirectErrorStream(true).start();
			if (syc) {
				in = new BufferedReader(new InputStreamReader(p.getInputStream(), CMD_ENCODE));
				String line = in.readLine();
				while (line != null) {
					if (buf.length() > 0) {
						buf.append(EOL);
					}
					buf.append(line);
					line = in.readLine();
				}
			}
			
		} catch (IOException e) {
			throw new RuntimeException(e);
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {
				}
			}
			if (p != null) {
				p.destroy();
			}
		}
		return buf.toString();
	}
    
    public static void main(String[] args) {
    	VideoUtil me = VideoUtil.getInstance("d:/mytools/ffmpeg.exe");
//    	int s = me.getVideoTime("d:/u0/1531554855549.mp4");
//    	System.out.println(s);
    	
//    	boolean ret = me.executeCodecs("d:/5609333776.mp4", "d:/5609333776.mov");
//    	System.out.println(ret);
    	
//    	boolean ret = me.screenImage("d:/5609333776.mp4", "d:/5609333776.jpg", 640, null);
//    	boolean ret = me.screenImage("d:/2790278452_.mov", "d:/2790278452_.jpg", 640, null);
//    	System.out.println(ret);
//    	boolean ret = me.screenImage("d:/u0/1531554855549.mp4", "d:/u0/test.jpg", 300, null, null);
//    	System.out.println(ret);
//    	boolean ret = me.screenImage("d:/u0/1531554855549.mp4", "d:/u0/test.jpg", null, 300, null);
//    	System.out.println(ret);
//    	String s1 = "Stream #0:0(eng): Video: h264 (Baseline) (avc1 / 0x31637661), yuv420p, 3840x2160, 8316 kb/s, SAR 1:1 DAR 16:9, 30.06 fps, 30 tbr, 90k tbn, 180k tbc (default)";
//    	String s2 = "Stream #0:0(eng): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 720x1280 [SAR 1:1 DAR 9:16], 1514 kb/s, 29.83 fps, 29.83 tbr, 11456 tbn, 59.67 tbc (default)";
//    	System.out.println(s1.replaceAll("\\((.*?)\\)", ""));
//    	System.out.println(s2.replaceAll("\\((.*?)\\)", ""));
	}
}
